﻿/*
____
 L|ypps Web controls for ASP.NET

Copyright (c) 2009 Alessandro Zifiglio, http://www.Typps.com
EMail: alessandro@Typps.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
namespace Typps
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;

    using NetSpell.SpellChecker;
    using NetSpell.SpellChecker.Dictionary;

    public class NetSpellDictionary
    {
        #region Fields

        const string expression = @"((?=&).\b{0}+\b(?=;).)|(\b{0}+\b)";
        const string templateFolder = "Typps.Silverlight.Resources.Templates.{0}";

        string _className;
        string _clientID;
        List<DictionaryItem> _dictionaries;
        bool _htmlEnableResults = true;
        string _ignoredWords;
        int _maxSuggestions = 4;
        Spelling _spellChecker;
        string _spellCheckerCompleteTemplate = "null";
        List<string> _spellCheckReplacementList = null;
        string _text = string.Empty;
        WordDictionary _wordDictionary;

        #endregion Fields

        #region Constructors

        public NetSpellDictionary(string spellCheckerCompleteTemplate, string clientID, string className)
        {
            if (!string.IsNullOrEmpty(spellCheckerCompleteTemplate))
                _spellCheckerCompleteTemplate = spellCheckerCompleteTemplate;
            _clientID = clientID;
            _className = className;
        }

        #endregion Constructors

        #region Events

        public event SpellCheckCompleteEventHandler SpellCheckComplete;

        #endregion Events

        #region Properties

        public List<DictionaryItem> Dictionaries
        {
            get
            {
                if (_dictionaries == null)
                    _dictionaries = new List<DictionaryItem>();
                return _dictionaries;
            }
        }

        /// <summary>
        /// When true, the default, the results returned by EndOfText event handler contains
        /// html around the misspelled words. When false a comma delimited
        /// string of misspelled words are returned allowing a more broader use
        /// case scenario.
        /// </summary>
        public bool HtmlEnableResults
        {
            get
            {
                return _htmlEnableResults;
            }
            set
            {
                _htmlEnableResults = value;
            }
        }

        public string IgnoredWords
        {
            get { return _ignoredWords; }
            set { _ignoredWords = value; }
        }

        /// <summary>
        ///     The maximum number of suggestions to generate
        /// </summary>
        public int MaxSuggestions
        {
            get { return _maxSuggestions; }
            set { _maxSuggestions = value; }
        }

        public string SpellCheckerCompleteTemplate
        {
            get
            {
                return _spellCheckerCompleteTemplate;
            }
        }

        /// <summary>
        /// The original raw text passed to the SpellCheck method
        /// </summary>
        public string Text
        {
            get
            {
                return _text;
            }
        }

        private List<string> SpellCheckReplacementList
        {
            get
            {
                if (_spellCheckReplacementList == null)
                    _spellCheckReplacementList = new List<string>();
                return _spellCheckReplacementList;
            }
        }

        #endregion Properties

        #region Methods

        public List<string> GetSuggestions(string path, string word)
        {
            return GetSuggestions(path, null, word);
        }

        public List<string> GetSuggestions(Stream dictionary, string word)
        {
            return GetSuggestions(null, dictionary, word);
        }

        public List<string> GetSuggestions(string path, Stream dictionary, string word)
        {
            Stream s = dictionary;
            if (!string.IsNullOrEmpty(path))
            {
                StreamReader sr = new StreamReader(path);
                s = sr.BaseStream;
            }
            using (s)
            {
                _wordDictionary = new WordDictionary(s);

                //load and initialize the dictionary
                _wordDictionary.Initialize();

                // create spell checker
                _spellChecker = new NetSpell.SpellChecker.Spelling(_wordDictionary);
                _spellChecker.MaxSuggestions = MaxSuggestions;
                _spellChecker.Suggest(word);
                return _spellChecker.Suggestions;
            }
        }

        public void SpellCheck(Stream dictionary, string text)
        {
            SpellCheck(null, dictionary, null, text);
        }

        public void SpellCheck(string path, string text)
        {
            SpellCheck(path, null, null, text);
        }

        public void SpellCheck(string path, Stream dictionary,
            string suggestionWord, string text)
        {
            _text = text;
            Stream s = dictionary;
            if (!string.IsNullOrEmpty(path))
            {
                StreamReader sr = new StreamReader(path);
                s = sr.BaseStream;
            }
            if (s == null) return;
            using (s)
            {
                _wordDictionary = new WordDictionary(s);

                //load and initialize the dictionary
                _wordDictionary.Initialize();

                // create spell checker
                _spellChecker = new NetSpell.SpellChecker.Spelling(_wordDictionary);
                if (!string.IsNullOrEmpty(text))
                {
                    _spellChecker.Text = text;
                    if (!string.IsNullOrEmpty(IgnoredWords))
                    {
                        _spellChecker.IgnoreList.Clear();
                        _spellChecker.IgnoreList.AddRange(
                            this.IgnoredWords.Split(new char[] { '|' },
                            StringSplitOptions.RemoveEmptyEntries));
                    }
                    _spellChecker.MisspelledWord +=
                        new Spelling.MisspelledWordEventHandler(spellChecker_MisspelledWord);
                    _spellChecker.DoubledWord +=
                        new Spelling.DoubledWordEventHandler(spellChecker_DoubledWord);
                    _spellChecker.EndOfText += new Spelling.EndOfTextEventHandler(spellChecker_EndOfText);
                    _spellChecker.SpellCheck();
                }
            }
        }

        internal string Render()
        {
            string template=string.Empty;
            if (SpellCheckReplacementList.Count != 0)
                template = SpellCheckerCompleteTemplate;
            if (HtmlEnableResults)
                return string.Format("{0}$spell$checker$complete$sep${1}$spell$checker$complete$sep${2}",
                    SpellCheckCorrectionMarkup(), template, SpellCheckReplacementList.Count);
            else
                return string.Join(",", SpellCheckReplacementList.ToArray());
        }

        protected virtual void OnSpellCheckComplete(SpellCheckCompleteEventArgs e)
        {
            if (SpellCheckComplete != null)
                SpellCheckComplete(this, e);
        }

        private DictionaryItem GetSelectedDictionaryFileName()
        {
            DictionaryItem item = null;
            foreach (DictionaryItem di in Dictionaries)
            {
                if (di.Selected)
                    return di;
            }
            if (Dictionaries.Count > 0)
            {
                item = Dictionaries[0];
                item.Selected = true;
            }
            return item;
        }

        private string SpellCheckCorrectionMarkup()
        {
            if (string.IsNullOrEmpty(_text)||SpellCheckReplacementList.Count==0) return string.Empty;
            int num1 = 0;
            StringBuilder sb = new StringBuilder(_text);
            foreach (string word in SpellCheckReplacementList)
            {
                Regex rx = new Regex(string.Format(expression, word));
                MatchCollection matches = rx.Matches(sb.ToString());
                int index = -1;
                int length = 0;
                foreach (Match m in matches)
                {
                    string substitute =
                        string.Format("<b id=\"{0}_{1}_misspellword\" class=\"{2}-misspelledword\">{3}</b>",
                        _clientID,++num1,_className, word);

                    if (index == -1)
                    {
                        index = m.Index;
                    }
                    else
                    {
                        length += (substitute.Length - word.Length);
                        index = m.Index + length;
                    }
                    sb = sb.Replace(word, substitute, index, word.Length);
                }
            }
            return sb.ToString();
        }

        void spellChecker_DoubledWord(object sender, SpellingEventArgs e)
        {
        }

        void spellChecker_EndOfText(object sender, EventArgs e)
        {
            string result = Render();
            SpellCheckCompleteEventArgs args = new SpellCheckCompleteEventArgs(result);
            OnSpellCheckComplete(args);
        }

        /*
            Note that when taking a full word such as "<b>h</b><i>e</i><u>l</u><em>l</em>" clearly when the formatting is removed, it will spell "hell" or
            when viewed in designmode, however parsing such a string is a bit tedious for replacement
            and the code does not take into account such a scenario.
        */
        void spellChecker_MisspelledWord(object sender, SpellingEventArgs e)
        {
            if (!SpellCheckReplacementList.Contains(e.Word))
                SpellCheckReplacementList.Add(e.Word);
            if (e.WordIndex <= e.TextIndex)
            {
                int wordIndex = e.WordIndex;
                _spellChecker.SpellCheck(++wordIndex);
            }
        }

        #endregion Methods
    }
}